function Request(method, params) {
	if (method === undefined) {
		throw new Error("Method must be specified");
	} else {
		this.m = method;
	}
	if (params === undefined) {
		this.p = {};
	} else {
		this.p = params;
	}
}

function Syncer() {
	this.indicator = document.getElementById("indicator");
	this.mapping = {};
	this.running = false;
	this.workers = {};
	this.workerPool = google.gears.factory.create('beta.workerpool');
	
	// register onmessage handler in workerPool
	var map = this.mapping;
	var s = this;
	this.workerPool.onmessage = function (a, b, message) {
		if (message.body.action !== undefined) {
			var action = message.body.action;
			if (map[action] !== undefined) {
				var callback = map[action];
				callback(s, message);
			} else {
				console.error("Unregistered action [" + action + "]");
			}
		} else {
			console.error("Message in wrong format.\n" + JSON.stringify(message));
		}
	};
	
	// register default actions
	this.registerAction("debug", this.debug);
	this.registerAction("info", this.info);
	this.registerAction("error", this.error);
	this.registerAction("setRunning", this.setRunning);
	this.registerAction("offline", this.offline);
	
	this.createSyncWorker();
}

Syncer.prototype.registerAction = function (action, callback) {
	if (this.mapping[action]) {
		throw new Error("Action already exists");
	} else {
		this.mapping[action] = callback;
	}
};

Syncer.prototype.createWorker = function (name, script) {
	if (this.workers[name]) {
		throw new Error("Worker with this name already exists");
	} else {
		this.workers[name] = this.workerPool.createWorker(script);
	}
};

Syncer.prototype.createSyncWorker = function () {
	var syncScript = 'var wp = google.gears.workerPool;\n' +
			'if (!this.JSON){this.JSON = {}; this.jsonInit = ' + String(jsonInit) + '; jsonInit(); }\n' +
			'var db = google.gears.factory.create("beta.database");\n' +
			'db.open("webdb.database");\n' +
			'var Request = ' + String(Request) + ';\n' +
			'var wSendRequest = ' + String(this.wSendRequest) + ';\n' + 
			'var wSendHistory = ' + String(this.wSendHistory) + ';\n' +
			'var wGetHistoryFromDb = ' + String(this.wGetHistoryFromDb) + ';\n' +
			'var wGetConfiremedVersion = ' + String(this.wGetConfiremedVersion) + ';\n' +
			'google.gears.workerPool.onmessage = ' + String(this.wHandleMessage) + ';\n';
//	console.debug(syncScript);
	this.createWorker("sync", syncScript);
};

Syncer.prototype.debug = function (s, message) {
	console.debug("#" + message.sender + " [DEBUG]: " + message.body.debug);
};

Syncer.prototype.info = function (s, message) {
	if (message.body.info === "DATA IS SYNCED") {
		info.info("Data is synced");
	}
	console.info("#" + message.sender + " [INFO]: " + message.body.info);
};

Syncer.prototype.error = function (s, message) {
	console.error("#" + message.sender + " [ERROR]: " + message);
};

Syncer.prototype.setRunning = function (s, message) {
//	console.debug("#" + message.sender + " [RUNNING]: " + message.body.running);
	var r = message.body.running;
	s.running = r.value;
	if (!s.running) {
		if (r.type === "error") {
			info.error(r.msg);
		} else if (r.type === "warn") {
			info.warn(r.msg);
		} else {
			info.info(r.msg);
		}
		var autosync = document.getElementById("autosync").checked;
		console.debug("autosync = " + autosync);
		if (autosync) {
			s.timer = window.setTimeout(function () { syncer.sync(); }, 30000);
		}
	}
	
	// refresh
	refreshTableList();
	if (s.component !== undefined) {
		if (s.component.name === "tableContent") {
			showTable(s.component.tableName);
		}
	}
};

Syncer.prototype.offline = function(s, message) {
	if (message.body.offline) {
		s.indicator.className = "offline";
		s.indicator.alt = "Offline mode";
	} else {
		s.indicator.className = "online";
		s.indicator.alt = "Online mode";
	}
};

Syncer.prototype.clearTimer = function() {
	if (this.timer) {
		window.clearTimeout(this.timer);
	}
};

Syncer.prototype.sync = function () {
	if (!this.running) {
		this.running = true;
//		console.info("Sync started");
		this.workerPool.sendMessage({action: "startSync"}, this.workers["sync"]);
	} else {
		console.info("Sync already running.");
	}
};

//=================================
// worker methods
//=================================

Syncer.prototype.wHandleMessage = function (a, b, message) {
	var action = message.body.action;
	if (action !== undefined) {
		if (action === "startSync") {
			this.wSendHistory(message);
		} else {
			wp.sendMessage({action: "error", error: "Unregistered action [" + action + "]"}, message.sender);
		}
	} else {
		wp.sendMessage({action: "error", error: "Message in wrong format.\n" + JSON.stringify(message)}, message.sender);
	}
};

Syncer.prototype.wSendRequest = function (message, request, callback, errorCallback) {
	
	if (typeof callback !== 'function') {
		return;
	}
	
	if (typeof errorCallback !== 'function') {
		return;
	}
	
	if (!request || request.m === undefined || request.p === undefined) {
		return;
	}
	var httpRequest = google.gears.factory.create('beta.httprequest');
	// TODO: support for "&" !!!
	httpRequest.open('GET', '/db?r=' + encodeURIComponent(JSON.stringify(request))); 
	httpRequest.onreadystatechange = function() {
		if (httpRequest.readyState == 4) {
			try {
				if (httpRequest.status == 200) {
					wp.sendMessage({action: "offline", offline: false}, message.sender);
					callback(httpRequest.responseText);
				} else {
					wp.sendMessage({action: "offline", offline: true}, message.sender);
					errorCallback({name: "StatusError", message: "status != 200"});
				}
			} catch (e){
				if (e.name === "Error" && e.message === "Internal error.") {
					wp.sendMessage({action: "offline", offline: true}, message.sender);
					errorCallback({name: "ConnectionError", message: "Connection to server failed. Working offline"});
				} else {
					errorCallback(e);
				}
			}
		}
	};
	httpRequest.send();
};

Syncer.prototype.wSendHistory = function (message) {
	var history = this.wGetHistoryFromDb(message);
	//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	//!!! THIS VERSION IS LAST SYNCED VERSION !!!!!!!!!
	//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	var version = this.wGetConfiremedVersion(message);
	
	var request = new Request("history", {"history": history, "version": version});
	wp.sendMessage({action: "debug", debug: "request = " + JSON.stringify(request)}, message.sender);
	this.wSendRequest(message, request, function (response) {
		var respObj;
		try {
			respObj = JSON.parse(response);
		} catch (e) {
			wp.sendMessage({action: "setRunning", running: {value: false, msg: "response was not proper JSON", type: "error"}}, message.sender);
			return;
		}
		wp.sendMessage({action: "debug", debug: "resposne = " + response}, message.sender);
		
		var historyEntries = respObj["history"];
		var metaActions = respObj["metaActions"];
		var actions = respObj["actions"];
		var synced = respObj["synced"];
		var msg = respObj["message"];
		
		if (synced) {
			wp.sendMessage({action: "info", info: "DATA IS SYNCED"}, message.sender);
			wp.sendMessage({action: "setRunning", running: {value: false, msg: msg, type: "info"}}, message.sender);
			return;
		}
		
		var rs;
		try {
			db.execute("BEGIN");
			
			// HISTORY
			wp.sendMessage({action: "debug", debug: "historyEntries = " + JSON.stringify(historyEntries)}, message.sender);
			for (var i in historyEntries) {
				var status = historyEntries[i]["status"];
				var hVersion = historyEntries[i]["version"];
				wp.sendMessage({action: "debug", debug: "status = " + status + " version = " + hVersion}, message.sender);
				db.execute("update __webdb_history set status = ? where version = ?", [status, hVersion]);
			}
			// END OF HISTORY
			
			// META ACTIONS
			wp.sendMessage({action: "debug", debug: "metaActions = " + JSON.stringify(metaActions)}, message.sender);
			for (var m = 0; m < metaActions.length; m++) {
				wp.sendMessage({action: "debug", debug: "m = " + m}, message.sender);
				var metaAction = metaActions[m];
				wp.sendMessage({action: "debug", debug: "metaAction.action = " + metaAction.action}, message.sender);
				if (metaAction.action === "restoreVersion") {
					wp.sendMessage({action: "debug", debug: "before rs"}, message.sender);
					rs = db.execute("SELECT version, reverse, reverseParams, reverseMetadata, reverseMetaParams from __webdb_history where version > ? order by version desc", [metaAction.version])
					wp.sendMessage({action: "debug", debug: "after rs"}, message.sender);
					while (rs.isValidRow()) {
						var dbVersion = rs.field(0);
						var dbReverse = rs.field(1);
						var dbReverseParams = rs.field(2);
						if (dbReverseParams) { dbReverseParams = JSON.parse(dbReverseParams); }
						var dbReverseMetadata = rs.field(3);
						var dbReverseMetaParams = rs.field(4);
						if (dbReverseMetaParams) { dbReverseMetaParams = JSON.parse(dbReverseMetaParams); }
						
						wp.sendMessage({action: "debug", debug: "Meta Actions: " + dbVersion}, message.sender);
						db.execute("DELETE FROM __webdb_history where version = ?", [dbVersion]);
						wp.sendMessage({action: "debug", debug: "Meta Actions: after delete " + dbVersion}, message.sender);
						db.execute("UPDATE sqlite_sequence SET seq = ? WHERE name = '__webdb_history'", [dbVersion - 1]); // there is - 1!!!!
						db.execute("UPDATE __webdb_schema SET version = ?", [dbVersion - 1]); // -1 !!!!!!
						wp.sendMessage({action: "debug", debug: "Meta Actions: after update " + dbVersion}, message.sender);
						
						wp.sendMessage({action: "debug", debug: "Meta Actions: dbReverse = " + dbReverse}, message.sender);
						if (dbReverseParams) {
							wp.sendMessage({action: "debug", debug: "Meta Actions: dbReverseParams = " + JSON.stringify(dbReverseParams)}, message.sender);
							db.execute(dbReverse, dbReverseParams);
						} else {
							wp.sendMessage({action: "debug", debug: "Meta Actions: no dbReverseParams"}, message.sender);
							db.execute(dbReverse);
						}
						
						wp.sendMessage({action: "debug", debug: "Meta Actions: dbReverseMetadata = " + dbReverseMetadata}, message.sender);
						if (dbReverseMetadata) {
							if (dbReverseMetaParams) {
								wp.sendMessage({action: "debug", debug: "Meta Actions: dbReverseMetaParams = " + JSON.stringify(dbReverseMetaParams)}, message.sender);
								db.execute(dbReverseMetadata, dbReverseMetaParams);
							} else {
								wp.sendMessage({action: "debug", debug: "Meta Actions: no dbReverseMetaParams"}, message.sender);
								db.execute(dbReverseMetadata);
							}
						}
						rs.next();
					}
				}
			}
			// END OF META ACTIONS
			
			// ACTIONS
			for (var j in actions) {
				var action = actions[j];
				var serverVersion = action["version"];
				
				var sql = action["sql"];
				var params = action["params"];
				var metadata = action["metadata"];
				var metadataParams = action["metadataParams"];
				
				var reverse = action["reverse"];
				var reverseParams = action["reverseParams"];
				var reverseMetadata = action["reverseMetadata"];
				var reverseMetaParams = action["reverseMetaParams"];
				
				var override = action["override"];
				wp.sendMessage({action: "debug", debug: "action = " + JSON.stringify(action)}, message.sender);
				
				if (params) {
					wp.sendMessage({action: "debug", debug: "params = " + JSON.stringify(params)}, message.sender);
					db.execute(sql, params);
				} else {
					wp.sendMessage({action: "debug", debug: "no params = " + JSON.stringify(params)}, message.sender);
					db.execute(sql);
				}
				if (metadata !== null) {
					if (metadataParams) {
						wp.sendMessage({action: "debug", debug: "metadataParams = " + JSON.stringify(metadataParams)}, message.sender);
						db.execute(metadata, metadataParams);
					} else {
						wp.sendMessage({action: "debug", debug: "no metadataParams = " + JSON.stringify(metadataParams)}, message.sender);
						db.execute(metadata);
					}
				}
				
				// convert arrays to strings
				params = params ? JSON.stringify(params) : null;
				metadataParams = metadataParams ? JSON.stringify(metadataParams) : null;
				reverseParams = reverseParams ? JSON.stringify(reverseParams) : null;
				reverseMetaParams = reverseMetaParams ? JSON.stringify(reverseMetaParams) : null;
				
				wp.sendMessage({action: "debug", debug: "before __webdb_history " + JSON.stringify(
						[serverVersion, action["internalId"], sql, params, 1, action["time"], 
						metadata, metadataParams, reverse, reverseParams, reverseMetadata, 
						reverseMetaParams])}, message.sender);
				
				db.execute("insert into __webdb_history (" +
						"version, internalId, sql, params, status, time, metadata, metadataParams, " +
						"reverse, reverseParams, reverseMetadata, reverseMetaParams) " +
						"values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", 
						[serverVersion, action["internalId"], sql, params, 1, action["time"], 
						metadata, metadataParams, reverse, reverseParams, reverseMetadata, 
						reverseMetaParams]
				);
				
				wp.sendMessage({action: "debug", debug: "after __webdb_history"}, message.sender);
				
				var localVersion = db.lastInsertRowId;
				if (override != 0) {
					db.execute("update __webdb_schema set version = ?", [localVersion]);
				}
			}
			// END OF ACTIONS
			
			db.execute("COMMIT");
			
			// ERROR HANDLING
			var error = respObj["error"];
			if (error) {
				wp.sendMessage({action: "setRunning", running: {value: false, msg: error, type: "error"}}, message.sender);
			}
			// END OF ERROR HANDLING
			
			// MESSAGE HANDLING
			if (msg) {
				wp.sendMessage({action: "setRunning", running: {value: false, msg: msg, type: "info"}}, message.sender);
			} else {
				wp.sendMessage({action: "setRunning", running: {value: false, msg: "Syncer finished work", type: "info"}}, message.sender);
			}
			// END OF MESSAGE HANDLING
		} catch (e) {
			wp.sendMessage({action: "debug", debug: "catch = " + JSON.stringify(e)}, message.sender);
			db.execute("ROLLBACK");
			wp.sendMessage({action: "setRunning", running: {value: false, msg: e.message, type: "error"}}, message.sender);
		} finally {
			if (rs) {
				rs.close();
			}
		}
		
	}, function (error) {
		wp.sendMessage({action: "debug", debug: "error in wSendHistory send request" + JSON.stringify(error)}, message.sender);
		var type = (error.name === "ConnectionError") ? "warn" : "error";
		wp.sendMessage({action: "setRunning", running: {value: false, msg: error.message, type: type}}, message.sender);
	});
};

Syncer.prototype.wGetHistoryFromDb = function (message) {
	var history = [];
	var historyRs;
	try {
		historyRs = db.execute("SELECT version, internalId, sql, params, time, metadata," +
				" metadataParams, reverse, reverseParams, reverseMetadata, reverseMetaParams" +
				" from __webdb_history where status = 0 order by version");
		
		while (historyRs.isValidRow()) {
			var params = historyRs.field(3) ? JSON.parse(historyRs.field(3)) : null;
			var metadataParams = historyRs.field(6) ? JSON.parse(historyRs.field(6)) : null;
			var reverseParams = historyRs.field(8) ? JSON.parse(historyRs.field(8)) : null;
			var reverseMetaParams = historyRs.field(10) ? JSON.parse(historyRs.field(10)) : null;
			history.push({
				version: historyRs.field(0), 
				internalId: historyRs.field(1), 
				sql: historyRs.field(2), 
				params: params,
				time: historyRs.field(4),
				metadata: historyRs.field(5),
				metadataParams: metadataParams,
				reverse: historyRs.field(7),
				reverseParams: reverseParams,
				reverseMetadata: historyRs.field(9),
				reverseMetaParams: reverseMetaParams
			});
			historyRs.next();
		}
	} catch (e) {
		history = [];
		wp.sendMessage({action: "error", error: "error in wGetHistoryFromDb " + JSON.stringify(error)}, message.sender);
	} finally {
		if (historyRs) {
			historyRs.close();
		}
	}
	return history;
};

//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//!!! THIS VERSION IS LAST SYNCED VERSION !!!!!!!!!
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Syncer.prototype.wGetConfiremedVersion = function (message) {
	var rs;
	try {
		rs = this.db.execute("SELECT max(version) FROM __webdb_history where status > 0");
		if (rs.isValidRow()) {
			var version = rs.field(0);
			if (version === null) { version = 0; }
			return version;
		} else {
			throw {name: "Error", message: "version unavailable"};
		}
	} finally {
		if (rs) {
			rs.close();
		}
	}
};